package com.ithuang.office.utils;

import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter;
import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.w3c.dom.Node;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 操作word文档工具类
 * @author HJW
 */
public class POIUtil {


    /**
     * 用一个docx文档作为模板，然后替换其中的内容，再写入目标文档中。
     * @throws Exception
     */
    public static void templateWrite(String filePath,String outFilePath,Map<String, Object> params) throws Exception {
        InputStream is = new FileInputStream(filePath);
        XWPFDocument doc = new XWPFDocument(is);
        //替换段落里面的变量
        replaceInPara(doc, params);
        //替换表格里面的变量
        replaceInTable(doc, params);
        OutputStream os = new FileOutputStream(outFilePath);
        doc.write(os);
        close(os);
        close(is);
    }

    /**
     * 替换段落里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    public static void replaceInPara(XWPFDocument doc, Map<String, Object> params) {
        Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
        XWPFParagraph para;
        while (iterator.hasNext()) {
            para = iterator.next();
            replaceInPara(para, params);
        }
    }

    /**
     * 替换段落里面的变量
     *
     * @param para   要替换的段落
     * @param params 参数
     */
    public static void replaceInPara(XWPFParagraph para, Map<String, Object> params) {
        List<XWPFRun> runs;
        Matcher matcher;
        String runText = "";
        int fontSize = 0;
        UnderlinePatterns underlinePatterns = null;
        if (matcher(para.getParagraphText()).find()) {
            runs = para.getRuns();
            if (runs.size() > 0) {
                int j = runs.size();
                for (int i = 0; i < j; i++) {
                    XWPFRun run = runs.get(0);
                    if (fontSize == 0) {
                        fontSize = run.getFontSize();
                    }
                    if(underlinePatterns==null){
                        underlinePatterns=run.getUnderline();
                    }
                    String i1 = run.toString();
                    runText += i1;
                    para.removeRun(0);
                }
            }
            matcher = matcher(runText);
            if (matcher.find()) {
                while ((matcher = matcher(runText)).find()) {
                    runText = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
                }
                //直接调用XWPFRun的setText()方法设置文本时，在底层会重新创建一个XWPFRun，把文本附加在当前文本后面，
                //所以我们不能直接设值，需要先删除当前run,然后再自己手动插入一个新的run。
                //para.insertNewRun(0).setText(runText);//新增的没有样式

                XWPFRun run = para.createRun();
                run.setText(runText,0);
                run.setFontSize(fontSize);
                run.setUnderline(underlinePatterns);
                run.setFontFamily("宋体");//字体
                run.setFontSize(10);//字体大小
                //run.setBold(true); //加粗
                //run.setColor("FF0000");
                //默认：宋体（wps）/等线（office2016） 5号 两端对齐 单倍间距
                //run.setBold(false);//加粗
                //run.setCapitalized(false);//我也不知道这个属性做啥的
                //run.setCharacterSpacing(5);//这个属性报错
                //run.setColor("BED4F1");//设置颜色--十六进制
                //run.setDoubleStrikethrough(false);//双删除线
                //run.setEmbossed(false);//浮雕字体----效果和印记（悬浮阴影）类似
                //run.setFontFamily("宋体");//字体
                //run.setFontFamily("华文新魏", FontCharRange.cs);//字体，范围----效果不详
                //run.setFontSize(14);//字体大小
                //run.setImprinted(false);//印迹（悬浮阴影）---效果和浮雕类似
                //run.setItalic(false);//斜体（字体倾斜）
                //run.setKerning(1);//字距调整----这个好像没有效果
                //run.setShadow(true);//阴影---稍微有点效果（阴影不明显）
                //run.setSmallCaps(true);//小型股------效果不清楚
                //run.setStrike(true);//单删除线（废弃）
                //run.setStrikeThrough(false);//单删除线（新的替换Strike）
                //run.setSubscript(VerticalAlign.SUBSCRIPT);//下标(吧当前这个run变成下标)---枚举
                //run.setTextPosition(20);//设置两行之间的行间距
                //run.setUnderline(UnderlinePatterns.DASH_LONG);//各种类型的下划线（枚举）
                //run0.addBreak();//类似换行的操作（html的  br标签）
                //run0.addTab();//tab键
                //run0.addCarriageReturn();//回车键
                //注意：addTab()和addCarriageReturn() 对setText()的使用先后顺序有关：比如先执行addTab,再写Text这是对当前这个Text的Table，反之是对下一个run的Text的Tab效果


            }
        }

    }


    /**
     * 替换表格里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    public static void replaceInTable(XWPFDocument doc, Map<String, Object> params) {
        Iterator<XWPFTable> iterator = doc.getTablesIterator();
        XWPFTable table;
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        while (iterator.hasNext()) {
            table = iterator.next();
            rows = table.getRows();
            for (XWPFTableRow row : rows) {
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    paras = cell.getParagraphs();
                    for (XWPFParagraph para : paras) {
                        replaceInPara(para, params);
                    }
                }
            }
        }
    }

    /**
     * 正则匹配字符串
     * @param str
     * @return
     */
    public static Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    /**
     * 关闭输入流
     * @param is
     */
    public static void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭输出流
     * @param os
     */
    public static void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * word转换成pdf
     * @param fileName
     * @param pdfName
     * @throws IOException
     */
    public static void wordToPdf(String fileName,String pdfName) throws IOException {
        InputStream in = Files.newInputStream(Paths.get(fileName));
        XWPFDocument document = new XWPFDocument(in);
        PdfOptions options = PdfOptions.create();
        OutputStream out = Files.newOutputStream(Paths.get(pdfName));
        PdfConverter.getInstance().convert(document,out,options);
    }


    /**
     * 替换书签
     *
     * @param document
     * @param bookTagMap 书签map
     */
    public static void replaceBookTag(XWPFDocument document, Map<String, Object> bookTagMap) {
        List<XWPFParagraph> paragraphList = document.getParagraphs();
        for (XWPFParagraph xwpfParagraph : paragraphList) {
            CTP ctp = xwpfParagraph.getCTP();

            for (int dwI = 0; dwI < ctp.sizeOfBookmarkStartArray(); dwI++) {
                CTBookmark bookmark = ctp.getBookmarkStartArray(dwI);
                if (bookTagMap.containsKey(bookmark.getName())) {

                    XWPFRun run = xwpfParagraph.createRun();
                    run.setText(bookTagMap.get(bookmark.getName()).toString());

                    Node firstNode = bookmark.getDomNode();
                    Node nextNode = firstNode.getNextSibling();
                    while (nextNode != null) {
                        // 循环查找结束符
                        String nodeName = nextNode.getNodeName();
                        if (nodeName.equals("w:bookmarkEnd")) {
                            break;
                        }

                        // 删除中间的非结束节点，即删除原书签内容
                        Node delNode = nextNode;
                        nextNode = nextNode.getNextSibling();

                        ctp.getDomNode().removeChild(delNode);
                    }

                    if (nextNode == null) {
                        // 始终找不到结束标识的，就在书签前面添加
                        ctp.getDomNode().insertBefore(run.getCTR().getDomNode(), firstNode);
                    } else {
                        // 找到结束符，将新内容添加到结束符之前，即内容写入bookmark中间
                        ctp.getDomNode().insertBefore(run.getCTR().getDomNode(), nextNode);
                    }
                }
            }
        }
    }

    /**
     * <b> Word中添加图章
     * </b><br><br><i>Description</i> :
     * String srcPath, 源Word路径
     * String storePath, 添加图章后的路径
     * String sealPath, 图章路径（即图片）
     * tString abText, 在Word中盖图章的标识字符串，如：(签字/盖章)
     * int width, 图章宽度
     * int height, 图章高度
     * int leftOffset, 图章在编辑段落向左偏移量
     * int topOffset, 图章在编辑段落向上偏移量
     * boolean behind，图章是否在文字下面
     * @return void
     * <br><br>Date: 2019/12/26 15:12
     */
    public static void sealInWord(String srcPath, String storePath,String sealPath,String tabText,
                                  int width, int height, int leftOffset, int topOffset, boolean behind) throws Exception {
        File fileTem = new File(srcPath);
        InputStream is = new FileInputStream(fileTem);
        XWPFDocument document = new XWPFDocument(is);
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        XWPFRun targetRun = null;
        for(XWPFParagraph  paragraph : paragraphs){
            if(!"".equals(paragraph.getText()) && paragraph.getText().contains(tabText)){
                List<XWPFRun> runs = paragraph.getRuns();
                targetRun = runs.get(runs.size()-1);
            }
        }
        if(targetRun != null){
            InputStream in = new FileInputStream(sealPath);//设置图片路径
            if(width <= 0){
                width = 100;
            }
            if(height <= 0){
                height = 100;
            }
            //创建Random类对象
            Random random = new Random();
            //产生随机数
            int number = random.nextInt(999) + 1;
            targetRun.addPicture(in, Document.PICTURE_TYPE_PNG, "Seal"+number, Units.toEMU(width), Units.toEMU(height));
            in.close();
            // 2. 获取到图片数据
            CTDrawing drawing = targetRun.getCTR().getDrawingArray(0);
            CTGraphicalObject graphicalobject = drawing.getInlineArray(0).getGraphic();

            //拿到新插入的图片替换添加CTAnchor 设置浮动属性 删除inline属性
            CTAnchor anchor = getAnchorWithGraphic(graphicalobject, "Seal"+number,
                    Units.toEMU(width), Units.toEMU(height),//图片大小
                    Units.toEMU(leftOffset), Units.toEMU(topOffset), behind);//相对当前段落位置 需要计算段落已有内容的左偏移
            drawing.setAnchorArray(new CTAnchor[]{anchor});//添加浮动属性
            drawing.removeInline(0);//删除行内属性
        }
        FileOutputStream outputStream = new FileOutputStream(storePath);
        document.write(outputStream);
        document.close();
        outputStream.close();
        is.close();
    }
    /**
     * @param ctGraphicalObject 图片数据
     * @param deskFileName      图片描述
     * @param width             宽
     * @param height            高
     * @param leftOffset        水平偏移 left
     * @param topOffset         垂直偏移 top
     * @param behind            文字上方，文字下方
     * @return
     * @throws Exception
     */
    public static CTAnchor getAnchorWithGraphic(CTGraphicalObject ctGraphicalObject,
                                                String deskFileName, int width, int height,
                                                int leftOffset, int topOffset, boolean behind) {
        String anchorXML =
                "<wp:anchor xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" "
                        + "simplePos=\"0\" relativeHeight=\"0\" behindDoc=\"" + ((behind) ? 1 : 0) + "\" locked=\"0\" layoutInCell=\"1\" allowOverlap=\"1\">"
                        + "<wp:simplePos x=\"0\" y=\"0\"/>"
                        + "<wp:positionH relativeFrom=\"column\">"
                        + "<wp:posOffset>" + leftOffset + "</wp:posOffset>"
                        + "</wp:positionH>"
                        + "<wp:positionV relativeFrom=\"paragraph\">"
                        + "<wp:posOffset>" + topOffset + "</wp:posOffset>" +
                        "</wp:positionV>"
                        + "<wp:extent cx=\"" + width + "\" cy=\"" + height + "\"/>"
                        + "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>"
                        + "<wp:wrapNone/>"
                        + "<wp:docPr id=\"1\" name=\"Drawing 0\" descr=\"" + deskFileName + "\"/><wp:cNvGraphicFramePr/>"
                        + "</wp:anchor>";

        CTDrawing drawing = null;
        try {
            drawing = CTDrawing.Factory.parse(anchorXML);
        } catch (XmlException e) {
            e.printStackTrace();
        }
        CTAnchor anchor = drawing.getAnchorArray(0);
        anchor.setGraphic(ctGraphicalObject);
        return anchor;
    }


    /**
     * 通过书签替换段落中的图片
     * @param doc
     * @param dataMap
     * @throws IOException
     * @throws InvalidFormatException
     */
    public static void replacePicByTag(XWPFDocument doc, Map<String, InputStream> dataMap) throws IOException, InvalidFormatException {
        List<XWPFParagraph> paragraphs = doc.getParagraphs();
        for (XWPFParagraph xwpfParagraph : paragraphs) {
            CTP ctp = xwpfParagraph.getCTP();

            for (int dwI = 0; dwI < ctp.sizeOfBookmarkStartArray(); dwI++) {
                CTBookmark bookmark = ctp.getBookmarkStartArray(dwI);

                InputStream picIs = dataMap.get(bookmark.getName());
                if(picIs != null){
                    XWPFRun run = xwpfParagraph.createRun();
                    //bus.png为鼠标在word里选择图片时，图片显示的名字，400，400则为像素单元，根据实际需要的大小进行调整即可。
                    run.addPicture(picIs,XWPFDocument.PICTURE_TYPE_PNG,"bus.png,", Units.toEMU(400), Units.toEMU(400));
                }
            }
        }

    }





}
